package com.supreme.commonmybatis.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.function.Consumer;

/**
 * sql解析工具类
 */
public class ParseSqlUtils {

    public static String parseSqlSession(JoinPoint joinPoint, SqlSessionFactory sqlSessionFactory, Consumer<BoundSql> boundSqlConsumer) throws IllegalAccessException {
        boundSqlConsumer.accept(null);

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String classTypeName = method.getDeclaringClass().getName();
        String methodName = method.getName();

        Map<String, Object> paramsMap = new HashMap<>();
        Object[] joinPointArgs = joinPoint.getArgs();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        for (int i = 0; i < parameterAnnotations.length; i++) {
            Object joinPointArg = joinPointArgs[i];
            if (parameterAnnotations[i].length == 0) {
                if (joinPointArg.getClass().getClassLoader() == null && joinPointArg instanceof Map) {
                    // 形参为Map
                    paramsMap.putAll((Map<? extends String, ?>) joinPointArg);
                } else {
                    // 形参为自定义实体类
                    paramsMap.putAll(objectToMap(joinPointArg));
                }
            } else {
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof Param) {
                        paramsMap.put(((Param) annotation).value(), joinPointArg);
                    }
                }
            }
        }

        Configuration configuration = sqlSessionFactory.getConfiguration();
        String classMethodName = String.join(".", classTypeName, methodName);
        MappedStatement mappedStatement = configuration.getMappedStatement(classMethodName);
        BoundSql boundSql = mappedStatement.getBoundSql(paramsMap);

//        boundSqlConsumer.accept(boundSql);

        return showSql(configuration, boundSql);
    }

    private static Map<String, Object> objectToMap(Object object) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = object.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(object);
            map.put(fieldName, value);
        }
        return map;
    }

    private static String getParameterValue(Object obj) {
        String value = "";
        if (obj != null) {
            if (obj instanceof String) {
                value = String.format("'%s'", obj);
            } else if (obj instanceof Date) {
                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
                value = String.format("'%s'", formatter.format(new Date()));
            } else {
                value = obj.toString();
            }
        }
        return value;
    }

    private static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    /**
     * 根据方法的FullName取得反射的Method
     * @param methodFullName
     * @param parameter
     * @return
     */
    public static Method getReflectMethodByMFN(String methodFullName, Object parameter) {
        String classTypeName = StringUtils.substringBeforeLast(methodFullName, ".");
        String methodName = StringUtils.substringAfterLast(methodFullName, ".");
        Method mapperMethod = null;

        try {
            Class<?> mapperClassType = Class.forName(classTypeName);
            mapperMethod = MethodUtils.getAccessibleMethod(mapperClassType, methodName, parameter.getClass());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return mapperMethod;
    }

    /**
     * 根据方法的FullName取得Method的注解
     * @param methodFullName
     * @param parameter
     * @param clazz
     * @param <TA>
     * @return
     */
    public static <TA extends Annotation> TA getMethodAnnotationByMFN(String methodFullName, Object parameter, Class<TA> clazz) {
        String classTypeName = StringUtils.substringBeforeLast(methodFullName, ".");
        String methodName = StringUtils.substringAfterLast(methodFullName, ".");
        TA annotation = null;

        try {
            Class<?> mapperClassType = Class.forName(classTypeName);
            Method mapperMethod = MethodUtils.getAccessibleMethod(mapperClassType, methodName, parameter.getClass());
            annotation = AnnotationUtils.findAnnotation(mapperMethod, clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return annotation;
    }
}
